#include <boost/geometry/extensions/contrib/ttmath_stub.hpp>
#include <boost/geometry/util/select_coordinate_type.hpp>

namespace mg
{
    namespace geometry
    {
        typedef double mgreal;

        namespace detail
        {
            template<typename T, bool IsFloatingPoint, typename Tolerance, bool IsToleranceFloatingPoint> 
            struct equals_with_epsilon
            {
                static inline bool apply(const T& a, const T& b)
                {
                   return boost::geometry::math::equals_with_epsilon<T, T>(a, b);
                }
            };

            template<typename T, typename Tolerance>
            struct equals_with_epsilon<T, true, Tolerance, true>
            {
                static inline bool apply(const T& a, const T& b)
                {
                    // Note that T is already at least as precise as mgreal.

                    // See http://www.parashift.com/c++-faq-lite/newbie.html#faq-29.17,
                    // FUTURE: replace by some boost tool or boost::test::close_at_tolerance
                    return std::abs(a - b) <= std::numeric_limits<Tolerance>::epsilon() * std::max(std::max(std::abs(a), std::abs(b)), T(1.0));

                    // see http://www.boost.org/doc/libs/1_35_0/libs/test/doc/components/test_tools/floating_point_comparison.html
                    // return std::abs(a - b) / std::abs(a) <= std::numeric_limits<Tolerance>::epsilon() && std::abs(a - b) / std::abs(b) <= std::numeric_limits<Tolerance>::epsilon();
                }
            };

            template<typename T, typename Tolerance>
            struct equals_with_epsilon<T, true, Tolerance, false>
            {
                static inline bool apply(const T& a, const T& b)
                {
                    // Note that T is already at least as precise as mgreal.

                    // See http://www.parashift.com/c++-faq-lite/newbie.html#faq-29.17,
                    // FUTURE: replace by some boost tool or boost::test::close_at_tolerance
                    return std::abs(a - b) <= std::numeric_limits<double>::epsilon() * std::max(std::max(std::abs(a), std::abs(b)), T(1.0));

                    // see http://www.boost.org/doc/libs/1_35_0/libs/test/doc/components/test_tools/floating_point_comparison.html
                    // return std::abs(a - b) / std::abs(a) <= std::numeric_limits<double>::epsilon() && std::abs(a - b) / std::abs(b) <= std::numeric_limits<double>::epsilon();
                }
            };

            template<typename Tolerance>
            struct equals_with_epsilon<ttmath_big, false, Tolerance, true>
            {
                static inline bool apply(const ttmath_big& a, const ttmath_big& b)
                {
                    const ttmath_big epsilon = std::numeric_limits<Tolerance>::epsilon();
                    //return ttmath::Abs(a - b) <= epsilon * ttmath::Abs(a);

                    // see http://www.boost.org/doc/libs/1_35_0/libs/test/doc/components/test_tools/floating_point_comparison.html
                    // return ttmath::Abs(a - b) / ttmath::Abs(a) <= epsilon && ttmath::Abs(a - b) / ttmath::Abs(b) <= epsilon;

                    return ttmath::Abs(a - b) <= epsilon;
                }
            };

            template<ttmath::uint Exponent, ttmath::uint Mantissa, typename Tolerance>
            struct equals_with_epsilon<ttmath::Big<Exponent, Mantissa>, false, Tolerance, true>
            {
                static inline bool apply(ttmath::Big<Exponent, Mantissa> const& a, ttmath::Big<Exponent, Mantissa> const& b)
                {
                    ttmath::Big<Exponent, Mantissa> const epsilon = std::numeric_limits<Tolerance>::epsilon();
                    return ttmath::Abs(a - b) <= epsilon;
                }
            };
        }

        template<typename T1, typename T2, typename Tolerance>
        inline bool equals_with_epsilon(const T1& a, const T2& b)
        {
            typedef boost::geometry::select_most_precise<typename boost::geometry::select_most_precise<T1, T2>::type, mgreal>::type T;
            return detail::equals_with_epsilon
                <
                T, 
                boost::is_floating_point<T>::type::value,
                Tolerance,
                boost::is_floating_point<Tolerance>::type::value
                >::apply(static_cast<T>(a), static_cast<T>(b));
        }

        template<typename T>
        struct coordinate_type_fp
        {
            typedef typename boost::geometry::select_most_precise<typename boost::geometry::coordinate_type<T>::type, mgreal>::type type;
        };

        template<typename T1, typename T2>
        struct select_coordinate_type_fp
        {
            typedef typename boost::geometry::select_most_precise<typename boost::geometry::select_coordinate_type<T1, T2>::type, mgreal>::type type;
        };
    }
}